 /* See LICENSE file for license details. */
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <wchar.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <limits.h>
#include <locale.h>

#include "config.h"

static char *host = DEFAULT_IRC_HOST;
static char *port = DEFAULT_IRC_PORT;
static char *password;
static int  auth_nickserv = 0;
static char *autojoin;
static char nick[32];
static int  curpos = 0;
static char bufsock[4096];
static wchar_t bufin[4096];
static char bufout[4096];
static char channel[256];
static time_t trespond;
static FILE *srv;
static struct termios oldterm;

#include "util.c"

#define CLREOL "\033[K"    /* Clear line from cursor position to the end */
#define CURHOME  "\033[0G" /* Move cursor to the beginning of line */
#define BELL "\a"

static int 
termset(int fd) {
    struct termios raw;

    if (!isatty(STDIN_FILENO)) goto fatal;
    if (tcgetattr(fd, &oldterm) == -1) goto fatal;

    raw = oldterm;
    raw.c_lflag &= ~(ECHO | ICANON);
    raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */

    if (tcsetattr(fd, TCSANOW, &raw) < 0) goto fatal;
    return 0;

fatal:
    errno = ENOTTY;
    return -1;
}

static void termrestore(int fd) {
	tcsetattr(fd,TCSAFLUSH, &oldterm);
}

static void 
prompt(void) {
	static char psbuf[256];
	struct winsize w;
	ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
	snprintf(psbuf, sizeof(psbuf)-1, PSFMT, PSARG);
	fprintf(stdout, CURHOME "%s", psbuf);

	if (wcslen(bufin) > w.ws_col - strlen(psbuf) - 1) {
		fprintf(stdout, "%ls", bufin + wcslen(bufin) + strlen(psbuf) 
			- w.ws_col + 1);
	} else {
		fprintf(stdout, "%ls", bufin);
	}
	fprintf(stdout, CLREOL CURHOME "\033[%dC", 
		curpos+strlen(psbuf));
	fflush(stdout);
}

static void
pout(char *channel, int bell, char *fmt, ...) {
	static char timestr[18];
	time_t t;
	va_list ap;

	va_start(ap, fmt);
	vsnprintf(bufout, sizeof bufout, fmt, ap);
	va_end(ap);
	t = time(NULL);
	strftime(timestr, sizeof timestr, timefmt, localtime(&t));
	fprintf(stdout, CURHOME CLREOL
	                COLOR_CHANNEL "%-12s:" COLOR_TIME " %s " COLOR_NORMAL "%s\n",
									channel, timestr, bufout);
	if (bell)
		fprintf(stdout, BELL);
}

static void
sout(char *fmt, ...) {
	va_list ap;

	va_start(ap, fmt);
	vsnprintf(bufout, sizeof bufout, fmt, ap);
	va_end(ap);
	fprintf(srv, "%s\r\n", bufout);
}

static void
privmsg(char *channel, char *msg) {
	if(channel[0] == '\0') {
		pout("", 0, "No channel to send to");
		return;
	}
	pout(channel, 0, COLOR_MYSELF "<%s>" COLOR_NORMAL " %s", nick, msg);
	sout("PRIVMSG %s :%s", channel, msg);
}

static void
parsein(char *s) {
	char c, *p;

	if(s[0] == '\0')
		return;
	skip(s, '\n');
	if(s[0] != ':') {
		privmsg(channel, s);
		return;
	}
	c = *++s;
	if(c != '\0' && isspace(s[1])) {
		p = s + 2;
		switch(c) {
		case 'j':
			sout("JOIN %s", p);
			if(channel[0] == '\0')
				strlcpy(channel, p, sizeof channel);
			return;
		case 'l':
			s = eat(p, isspace, 1);
			p = eat(s, isspace, 0);
			if(!*s)
				s = channel;
			if(*p)
				*p++ = '\0';
			if(!*p)
				p = "sec - next to sic";
			sout("PART %s :%s", s, p);
			return;
		case 'm':
			s = eat(p, isspace, 1);
			p = eat(s, isspace, 0);
			if(*p)
				*p++ = '\0';
			privmsg(s, p);
			return;
		case 's':
			strlcpy(channel, p, sizeof channel);
			return;
		}
	}
	sout("%s", s);
}

static void
parsesrv(char *cmd) {
	char *usr, *par, *txt;

	usr = host;
	if(!cmd || !*cmd)
		return;
	if(cmd[0] == ':') {
		usr = cmd + 1;
		cmd = skip(usr, ' ');
		if(cmd[0] == '\0')
			return;
		skip(usr, '!');
	}
	skip(cmd, '\r');
	par = skip(cmd, ' ');
	txt = skip(par, ':');
	trim(par);
	if(!strcmp("PONG", cmd))
		return;
	if(!strcmp("PRIVMSG", cmd))
		pout(par, BELL_USRMSG, COLOR_NICK "<%s>" COLOR_NORMAL " %s", usr, txt);
	else if(!strcmp("PING", cmd))
		sout("PONG %s", txt);
	else {
		char *endptr;
		int id;
		id = strtol(cmd, &endptr, 10);
		if (id == 1) {
			if (auth_nickserv && password != NULL)
				sout("PRIVMSG NickServ IDENTIFY %s", password);
			if (autojoin != NULL) {
				sout("JOIN %s", autojoin);
				strlcpy(channel, autojoin, sizeof channel);
			}
		}
		pout(usr, BELL_SRVMSG, COLOR_COMMAND ">< %s (%s): %s" COLOR_NORMAL, cmd, par, txt);
		if(!strcmp("NICK", cmd) && !strcmp(usr, nick))
			strlcpy(nick, txt, sizeof nick);
	}
}

static void
parsekey(wchar_t c) {
	int  lenmb;
	char *bufmb;
	int len = wcslen(bufin);
	switch (c) {
		case 127:		/* backspace */
		case L'\b':
			if (curpos > 0) {
				memmove(bufin+curpos-sizeof(c), bufin+curpos, len-curpos);
				bufin[--curpos] = '\0';
			}
			break;
		case L'\n':	/* return */
		  lenmb = wcstombs(NULL, bufin, 0);
			if (lenmb <= 0) break;
			bufmb = malloc(lenmb+1);
			if (bufmb == NULL) break;
			memset(bufmb, 0, lenmb+1);
			wcstombs(bufmb, bufin, lenmb);
			parsein(bufmb);
			free(bufmb);
			memset(bufin, 0, sizeof(bufin));
			curpos = 0;
			break;
		case 0x0c: /* Ctrl+L */
			fprintf(stdout, "\033[2J\033[H");
			break;
		default:
			memmove(bufin+curpos+sizeof(c), bufin+curpos, len-curpos);
			bufin[curpos] = c;
			curpos++;
	}
}

int
main(int argc, char *argv[]) {
	int i, c;
	struct timeval tv;
	const char *user = getenv("USER");
	fd_set rd;

	strlcpy(nick, user ? user : DEFAULT_IRC_NICK, sizeof nick);
	for(i = 1; i < argc; i++) {
		c = argv[i][1];
		if(argv[i][0] != '-' || argv[i][2])
			c = -1;
		switch(c) {
		case 'h':
			if(++i < argc) host = argv[i];
			break;
		case 'p':
			if(++i < argc) port = argv[i];
			break;
		case 'n':
			if(++i < argc) strlcpy(nick, argv[i], sizeof nick);
			break;
		case 'k':
			if(++i < argc) password = argv[i];
			break;
		case 'N':
			auth_nickserv = 1;
			break;
		case 'j':
			if (++i < argc) autojoin = argv[i];
			break;
		case 'v':
			eprint("sec-"VERSION", © 2005-2010 Kris Maglione, Anselm R. Garbe, Nico Golde, Serge A. Zaitsev\n");
		default:
			eprint("usage: sec [-h host] [-p port] [-n nick] [-k keyword] [-v]\n");
		}
	}
	/* init */
	i = dial(host, port);
	srv = fdopen(i, "r+");
	/* login */
	if(auth_nickserv ==0 && password)
		sout("PASS %s", password);
	sout("NICK %s", nick);
	sout("USER %s localhost %s :%s", nick, host, nick);
	fflush(srv);
	setbuf(stdout, NULL);
	setbuf(srv, NULL);
	termset(STDIN_FILENO);
	prompt();

	setlocale(LC_ALL, "");

	for(;;) { /* main loop */
		FD_ZERO(&rd);
		FD_SET(0, &rd);
		FD_SET(fileno(srv), &rd);
		tv.tv_sec = 120;
		tv.tv_usec = 0;
		i = select(fileno(srv) + 1, &rd, 0, 0, &tv);
		if(i < 0) {
			if(errno == EINTR)
				continue;
			eprint("%ssec: error on select():",
				BELL_FATAL ? BELL : "");
		}
		else if(i == 0) {
			if(time(NULL) - trespond >= 300)
				eprint("sec shutting down: parse timeout\n");
			sout("PING %s", host);
			continue;
		}
		if(FD_ISSET(fileno(srv), &rd)) {
			if(fgets(bufsock, sizeof bufsock, srv) == NULL)
				eprint("sec: remote host closed connection\n");
			parsesrv(bufsock);
			trespond = time(NULL);
		}
		if(FD_ISSET(0, &rd)) {
			int r;
			wchar_t wc;
			char ch[MB_CUR_MAX];
			if ((r = read(STDIN_FILENO, ch, sizeof(ch))) <= 0)
				eprint("sec: broken pipe\n");
			for (i = 0; i<r; ) {
				int w = mbtowc(&wc, &ch[i], r-i);
				i+=w;
				parsekey(wc);
			}
		}
		prompt();
	}
	termrestore(STDIN_FILENO);
	return 0;
}
